<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<title>Ride Share Map</title>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
/*
Calculate the detour distance between two different rides. 
Given four lattitude / longitude pairs, where driver one is traveling from point A to point B and driver two is traveling from point C to point D, 
write a function (in your language of choice) to calculate the shorter of the detour distances the drivers would need to take to pick-up and drop-off the other driver.
- see http://www.zimride.com/jobs#director

Bob Hitching, 2010
http://hitching.net - mobile geo social
*/

var map;
var directionsService = new google.maps.DirectionsService();
var markers = {};
var drivers = [];

$().ready(function() {
	// setup the map
	$('#map_canvas').css({
		height: $(window).height() - $('#map_canvas').offset().top,
		width: $(window).width() - 50
	});
	var myOptions = {
		zoom: 11,
		mapTypeId: google.maps.MapTypeId.ROADMAP,
		center: new google.maps.LatLng(37.663, -122.255)
	}
	map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
	
	// Given four lattitude / longitude pairs ...
	new RidePoint({key: 'A', position: new google.maps.LatLng(37.8141, -122.2717)});
	new RidePoint({key: 'B', position: new google.maps.LatLng(37.7805, -122.4062)});
	new RidePoint({key: 'C', position: new google.maps.LatLng(37.7664, -122.1817)});
	new RidePoint({key: 'D', position: new google.maps.LatLng(37.5751, -122.3286)});
	
	// ... where driver one is traveling from point A to point B ...
	addDriver({
		name: 'One',
		from: 'A', to: 'B',
		via: ['C', 'D'],
		color: 'FF0000'
	});
	
	// ... and driver two is traveling from point C to point D ...
	addDriver({
		name: 'Two',
		from: 'C', to: 'D',
		via: ['A', 'B'],
		color: '00FF00'
	});	

	// ... calculate the shorter of the detour distances the drivers would need to take to pick-up and drop-off the other driver
	calculateShortestDetour();
});

// ... calculate the shorter of the detour distances the drivers would need to take to pick-up and drop-off the other driver
function calculateShortestDetour() {
	// array of routes
	var routes = [];
	
	hideResult();

	$.each(drivers, function(key, driver) {
		// DirectionsRequest object, see http://code.google.com/apis/maps/documentation/javascript/reference.html#DirectionsRequest
	    var directionsRequest = {
	        origin: markers[driver.from].getPosition(), 
	        destination: markers[driver.to].getPosition(),
			waypoints: $.map(driver.via, function (waypoint) {
				return { location: markers[waypoint].getPosition() }
			}),
	        travelMode: google.maps.DirectionsTravelMode.DRIVING
	    };
		
	    directionsService.route(directionsRequest, function(result, status) {
			// add each route as it is returned
			routes.push({
				driver: driver,
				directionsResult: result,
				directionsStatus: status
			});
			
			// if all the routes have returned, show the result
			if (routes.length == drivers.length) showResult(routes);
		});	
	});
}

// display a subtitle and polylines to explain the shortest detour
function showResult(routes) {
	$.each(routes, function(key, route) {
		route.shared_distance = 0;

		if (route.directionsStatus == google.maps.DirectionsStatus.OK) {
			// calculate the total shared distance
			$.each(route.directionsResult.routes[0].legs, function(key, leg) {
				route.shared_distance += leg.distance.value;
			});
			
			// the middle leg equals the direct unshared path from the other route, and saves us making another directionsRequest
			routes[1-key].unshared_distance = route.directionsResult.routes[0].legs[1].distance.value;
		} else {
			routes[1-key].unshared_distance = 0;
		}
	});

	// calculate the detour and saving of each route
	$.each(routes, function(key, route) {
		route.detour_distance = route.shared_distance - route.unshared_distance;
		route.saving = route.unshared_distance + routes[1-key].unshared_distance - route.shared_distance;
	});

	// which is the shortest detour?
	routes.sort(function(a, b) {
		return a.detour_distance - b.detour_distance;
	});

	shortestDetour = routes[0];
	
	// if the shortest detour doesn't save us any petrol, don't share. okay, find another ride.
	if (shortestDetour.saving < 0) {
		$('#result').html('Don\'t Share! There is no benefit in these drivers sharing a ride. They should <a href="http://zimride.com">find someone else</a> to share with!');
		
		// render two separate routes
		$.each(drivers, function(key, driver) {
			var directionsRequest = {
				origin: markers[driver.from].getPosition(), 
				destination: markers[driver.to].getPosition(),
				travelMode: google.maps.DirectionsTravelMode.DRIVING
			};
			directionsService.route(directionsRequest, function(result, status) {
				driver.directionsDisplay.setOptions({
					directions: result,
					map: map,
					polylineOptions: {
						clickable: false,
						strokeColor: '#' + driver.color
					},
					suppressMarkers: true,
					preserveViewport: true
				});
			});
		});
		
	} else {
		var route = [shortestDetour.driver.from].concat(shortestDetour.driver.via).concat([shortestDetour.driver.to]).join('&raquo;');
		$('#result').html('Share! The shortest detour is ' + shortestDetour.detour_distance.mToMiles() + ' for Driver '+ shortestDetour.driver.name + ' travelling ' + route + ', which saves ' + shortestDetour.saving.mToMiles() + ' of fuel.');
		
		shortestDetour.driver.directionsDisplay.setOptions({
			directions: shortestDetour.directionsResult,
			map: map,
			polylineOptions: {
				clickable: false,
				strokeColor: '#' + shortestDetour.driver.color
			},
			suppressMarkers: true
		});
	}

	$('#warnings').html(shortestDetour.directionsResult.routes[0].warnings.join('<br/>'));
	$('#copyrights').html(shortestDetour.directionsResult.routes[0].copyrights);
}

// hide the subtitle and polylines
function hideResult() {
	$('#result').html('Calculating...');
	
	$.each(drivers, function(key, driver) {
		if (driver.directionsDisplay) driver.directionsDisplay.setMap(null);
	});
}

// RidePoint extends Marker to provide colored keys and dragging goodness
RidePoint.prototype = new google.maps.Marker();
function RidePoint(options) {
	this.key = options.key;

	this.setColor = function(color) {
		this.setIcon('http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=' + this.key + '|' + color + '|000000');
	};
	
	this.setOptions({
		position: options.position,
		draggable: true,
		map: map
	});
	
	google.maps.event.addListener(this, 'dragstart', function() {
		hideResult();
	});

	google.maps.event.addListener(this, 'dragend', function() {
		calculateShortestDetour();
	});
	
	markers[this.key] = this;
}

// adding a driver involves setting the color of from/to markers
function addDriver(driver) {
	markers[driver.from].setColor(driver.color);
	markers[driver.to].setColor(driver.color);
	
	driver.directionsDisplay = new google.maps.DirectionsRenderer();
	drivers.push(driver);
}

// convert a number in meters to miles
Number.prototype.mToMiles = function() {
	var miles = this/1609.344;
	return (miles >= 100 ? Math.round(miles) : miles.toPrecision(2)) + ' miles';
};
</script>
<style type="text/css">
body { font-family: Verdana, sans-serif; font-size: 10pt; }
#result, #warnings, #copyrights { margin-bottom: 10px; }
</style>
</head>
<body>
Given four latitude / longitude pairs, 
where Driver One is traveling from point A to point B and Driver Two is traveling from point C to point D, 
calculate the shorter of the detour distances the drivers would need to take to pick-up and drop-off the other driver...
<h2>Drag the markers to recalculate the shortest detour</h2>
<div id="result">Loading...</div>
<div id="map_canvas"></div>
<div id="warnings"></div>
<div id="copyrights"></div>
</body>
</html>